package com.uxsino.simo.collector.connections;

import java.util.Map;

import org.bson.Document;
import org.bson.conversions.Bson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.mongodb.BasicDBObject;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoClientURI;
import com.mongodb.MongoCredential;
import com.mongodb.MongoException;
import com.mongodb.ServerAddress;
import com.mongodb.client.MongoDatabase;
import com.uxsino.simo.connections.AbstractConnection;
import com.uxsino.simo.connections.exception.SimoQueryException;
import com.uxsino.simo.connections.target.MongoDBTarget;

public class MongoDBConnection extends AbstractConnection<MongoDBTarget> {
    private static Logger logger = LoggerFactory.getLogger(MongoDBConnection.class);

    private MongoClient client;

    private MongoDatabase database;

    private int CONNECTION_TIME_OUT_MS = 5000;

    private int SOCKET_TIME_OUT_MS = 5000;

    private int SERVER_SELECTION_TIMEOUT_MS = 5000;

    @Override
    public int connect(MongoDBTarget target) {
        super.connect(target);
        state = 0;
        if (StringUtils.isEmpty(target.getUsername()) && StringUtils.isEmpty(target.getPassword())) {
            client = new MongoClient(
                new MongoClientURI("mongodb://" + target.host + ":" + target.port + "/" + target.getDbName()));
        } else {
            MongoCredential cred = MongoCredential.createCredential(target.getUsername(), target.getDbName(),
                target.getPassword().toCharArray());

            MongoClientOptions.Builder optionsBuilder = MongoClientOptions.builder();

            optionsBuilder.connectTimeout(CONNECTION_TIME_OUT_MS);

            optionsBuilder.socketTimeout(SOCKET_TIME_OUT_MS);

            optionsBuilder.serverSelectionTimeout(SERVER_SELECTION_TIMEOUT_MS);

            MongoClientOptions options = optionsBuilder.build();

            client = new MongoClient(new ServerAddress(target.host, target.port), cred, options);
        }
        try {
            // client = new MongoClient(getClientURI(target));
            database = client.getDatabase(target.getDbName());

            connected = true;
            state = 1;
        } catch (MongoException | IllegalArgumentException e) {
            logger.error("mongodb connect failed", e);
            connected = false;
            return state;
        }

        return state;
    }

    @Override
    public Object execCmd(Object cmd) throws SimoQueryException {
        String resultJson = null;
        try {
            Bson cmdObj = BasicDBObject.parse((String) cmd);
            Document resultDoc = database.runCommand(cmdObj);
            resultJson = resultDoc.toJson();
            return resultJson;
        } catch (Exception e) {
            logger.error("mongodb execCmd exception ", e);
            throw new SimoQueryException(e);
        }

    }

    @Override
    public Object buildCmd(String cmdPattern, Map<String, String> args) {
        return cmdPattern;
    }

    @Override
    public int close() {
        try {
            client.close();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        connected = false;
        super.close();
        return 0;
    }

    @Override
    public boolean testWithConnected(String cmdString, String resStart) {
        Object cmdRes = null;
        try {
            cmdRes = execCmd(cmdString);
        } catch (SimoQueryException e) {
            logger.error("execute command failedd : {}", e);
        }
        if (cmdRes == null) {
            close();
            logger.info("mongodb connection test failed");
            return false;
        }
        JSONObject resJson = (JSONObject) JSON.parse((String) cmdRes);
        Object obj = resJson.get(resStart);
        if (obj == null) {
            connected = false;
        }
        logger.info("mongodb connection test result: " + connected);
        return connected;
    }

}
